// ============================================================================
// FileName: RegistrarCore.cs
//
// Description:
// SIP Registrar that strives to be RFC3822 compliant.
//
// Author(s):
// Aaron Clauson
//
// History:
// 21 Jan 2006	Aaron Clauson	Created.
// 22 Nov 2007  Aaron Clauson   Fixed bug where binding refresh was generating a duplicate exception if the uac endpoint changed but the contact did not.
//
// License: 
// This software is licensed under the BSD License http://www.opensource.org/licenses/bsd-license.php
//
// Copyright (c) 2006-2007 Aaron Clauson (aaronc@blueface.ie), Blue Face Ltd, Dublin, Ireland (www.blueface.ie)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are permitted provided that 
// the following conditions are met:
//
// Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 
// Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following 
// disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of Blue Face Ltd. 
// nor the names of its contributors may be used to endorse or promote products derived from this software without specific 
// prior written permission. 
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 
// BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, 
// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 
// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
// POSSIBILITY OF SUCH DAMAGE.
// ============================================================================

using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Net;
using System.Net.Sockets;
using System.Runtime.Serialization;
using System.Text.RegularExpressions;
using System.Threading;
using System.Xml;
using System.Xml.Serialization;
using BlueFace.Sys.Net;
using BlueFace.VoIP.Authentication;
using BlueFace.VoIP.Net;
using BlueFace.VoIP.Net.SIP;
using BlueFace.Sys;
using log4net;

#if UNITTEST
using NUnit.Framework;
#endif

namespace BlueFace.VoIP.SIPServer
{
    public delegate SIPRegistrarRecord LoadRegistrarRecordDelegate(SIPParameterlessURI addressOfRecord);
    public delegate void PersistRegistrarRecordDelegate(SIPRegistrarRecord registrarRecord);
    public delegate void SIPBindingExpired(SIPAddressBinding sipAddressBinding, SIPBindingRemovalReason removalReason);
    public delegate void SendNATKeepAliveDelegate(NATKeepAliveMessage keepAliveMessage);

    public enum RegisterResultEnum
    {
        Unknown = 0,
        Trying = 1,
        Forbidden = 2,
        Authenticated = 3,
        AuthenticationRequired = 4,
        Failed = 5,
        Error = 6,
        RequestWithNoUser = 7,
        RemoveAllRegistrations = 9,
        DuplicateRequest = 10,
        AuthenticatedFromCache = 11,
        RequestWithNoContact = 12,
        NonRegisterMethod = 13,
        DomainNotServiced = 14,
    }

    public enum SIPBindingRemovalReason
    {
        Unknown = 0,
        ClientExpiredSpecific = 1,
        ClientExpiredAll = 2,
        ExceededPerUserLimit = 3,
        OptionsTimedOut = 4,
        OptionsErrorResponse = 5,
        MaxLifetimeReached = 6,
    }

	/// <summary>
    /// The registrar core is the class that actually does the work of receiving registration requests and populating and
    /// maintaining the SIP registrations list.
    /// 
    /// From RFC 3261 Chapter "10.2 Constructing the REGISTER Request"
    /// - Request-URI: The Request-URI names the domain of the location service for which the registration is meant.
    /// - The To header field contains the address of record whose registration is to be created, queried, or modified.  
    ///   The To header field and the Request-URI field typically differ, as the former contains a user name. 
    /// 
    /// [ed Therefore:
    /// - The Request-URI inidcates the domain for the registration and should match the domain in the To address of record.
    /// - The To address of record contians the username of the user that is attempting to authenticate the request.]
    /// 
    /// Method of operation:
    ///  - New SIP messages received by the SIP Transport layer and queued before being sent to RegistrarCode for processing. For requests
    ///    or response that match an existing REGISTER transaction the SIP Transport layer will handle the retransmit or drop the request if
    ///    it's already being processed.
    ///  - Any non-REGISTER requests received by the RegistrarCore are responded to with not supported,
    ///  - If a persistence is being used to store registered contacts there will generally be a number of threads running for the
    ///    persistence class. Of those threads there will be one that runs calling the SIPRegistrations.IdentifyDirtyContacts. This call identifies
    ///    expired contacts and initiates the sending of any keep alive and OPTIONs requests.
	/// </summary>
    public class RegistrarCore
	{	
		private const int CACHE_EXPIRY_TIME = 10;			// Time in minutes a SIP account can exist in the cache and use a previous registration before a new auth will be requested, balance with agressive NAT timeouts.
															// a random element is also used in conjunction with this to attempt to mitigate registration spikes.
        private const int DEFAULT_BINDINGS_PER_USER = 1;    // The default maixmim number of bindings that will be allowed for each unique SIP account.
        public const string PROXY_VIA_PARAMETER_NAME = "proxy";    // A proxy forwarding REGISTER requests will add a parameter with this name and a value of the socket it received the request on.

        public const int DEFAULT_EXPIRY_SECONDS = 180;
        public const int MAXIMUM_EXPIRY_SECONDS = 3600;

		private static ILog logger = AppState.GetLogger("registrar");

        private SIPTransport m_sipTransport;
        private SIPRegistrations m_registrationsStore;

        private string m_serverAgent;                    
        private bool m_mangleUACContact = false;            // Whether or not to adjust contact URIs that contain private hosts to the value of the bottom via received socket.
        private bool m_strictRealmHandling = false;         // If true the registrar will only accept registration requests for domains it is configured for, otherwise any realm is accepted.
        private event ProxyLogDelegate m_registrarLogEvent;
        private int m_maximumUserBindings = DEFAULT_BINDINGS_PER_USER;
        private Dictionary<string, SIPUserAgentConfiguration> m_userAgentConfigs;

        public RegistrarCore(
            SIPTransport sipTransport,
            SIPRegistrations registrationsStore,
            bool mangleUACContact,
            bool strictRealmHandling,
            string serverAgent,
            int maximumUserBindings,
            ProxyLogDelegate proxyLogDelegate,
            Dictionary<string, SIPUserAgentConfiguration> userAgentConfigs)
        {
            m_sipTransport = sipTransport;
            m_registrationsStore = registrationsStore;
            m_mangleUACContact = mangleUACContact;
            m_strictRealmHandling = strictRealmHandling;
            m_serverAgent = serverAgent;
            m_maximumUserBindings = maximumUserBindings;
            m_registrarLogEvent = proxyLogDelegate;
            m_userAgentConfigs = userAgentConfigs;
        }

		public void AddRegisterRequest(SIPProtocolsEnum protocol, IPEndPoint localEndPoint, IPEndPoint remoteEndPoint, SIPRequest registerRequest)
		{
            try
            {
                if (registerRequest.Method != SIPMethodsEnum.REGISTER)
                {
                    SIPResponse notSupportedResponse = GetErrorResponse(registerRequest.Header, SIPResponseStatusCodesEnum.MethodNotAllowed, "Registration requests only");
                    m_sipTransport.SendResponse(notSupportedResponse);
                }
                else
                {
                    SIPNonInviteTransaction registrarTransaction = m_sipTransport.CreateNonInviteTransaction(registerRequest, remoteEndPoint, localEndPoint, protocol);

                    if (registrarTransaction.TransactionCreationError == SIPValidationError.None)
                    {
                        DateTime registerStartTime = DateTime.Now;
                        RegisterResultEnum result = Register(registrarTransaction);
                        TimeSpan registerTime = DateTime.Now.Subtract(registerStartTime);

                        FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.Registrar, ProxyEventTypesEnum.RegistrarTiming, "register result=" + result.ToString() + ", time=" + registerTime.TotalMilliseconds.ToString("0") + "ms, user=" + registrarTransaction.TransactionRequest.Header.To.ToURI.User + ".", null));
                    }
                    else
                    {
                        FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.Registrar, ProxyEventTypesEnum.Warn, "A register request from " + remoteEndPoint + " was not processed due to a transaction creation error, " + registrarTransaction.TransactionCreationError + ".", null));

                        try
                        {
                            SIPResponse errorResponse = GetErrorResponse(registerRequest.Header, SIPResponseStatusCodesEnum.BadRequest, registrarTransaction.TransactionCreationError.ToString());
                            m_sipTransport.SendResponseFrom(localEndPoint, remoteEndPoint, protocol, errorResponse);
                        }
                        catch (Exception badReqExcp)
                        {
                            logger.Error("Exception AddRegisterRequest (responding to bad request). " + badReqExcp);
                        }
                    }
                }
            }
            catch (Exception excp)
            {
                logger.Error("Exception AddRegisterRequest (" + remoteEndPoint.ToString() + "). " + excp.Message);
            }
		}

        private RegisterResultEnum Register(SIPTransaction registerTransaction)
        {
            SIPRequest sipRequest = registerTransaction.TransactionRequest;
            SIPURI registerURI = sipRequest.URI;
            SIPToHeader toHeader = sipRequest.Header.To;
            bool authenticated = false;
            SIPParameterlessURI addressOfRecord = null;

            try
            {
                #region Check that register request is valid.

                if (toHeader.ToURI.User == null || toHeader.ToURI.User.Trim().Length == 0)
                {
                    // Ignore empty usernames.
                    FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.Registrar, ProxyEventTypesEnum.Warn, "Request had empty user responding with Forbidden, to header=" + toHeader.ToString() + ".", null));
                    SIPResponse forbiddenResponse = GetErrorResponse(sipRequest.Header, SIPResponseStatusCodesEnum.Forbidden, "Forbidden, the username was empty");
                    registerTransaction.SendFinalResponse(forbiddenResponse);
                    return RegisterResultEnum.RequestWithNoUser;
                }

                #endregion

                addressOfRecord = new SIPParameterlessURI(toHeader.ToURI);

                //FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.Registrar, ProxyEventTypesEnum.Registrar, "Registration for " + username + ", contact header received " + origContact + " using " + contactStr + ".", null, null, null, null));

                // Check cache for recent registrations.
                SIPRegistrarRecord registration = m_registrationsStore.Lookup(addressOfRecord);
                /*if (registration != null && registration.LastAuthenticationTime != DateTime.MinValue &&
                    DateTime.Now.Subtract(registration.LastAuthenticationTime).TotalMinutes < CACHE_EXPIRY_TIME)
                {
                    FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.Registrar, ProxyEventTypesEnum.RegistrarCache, "authenticating user=" + addressOfRecord.User + " from cache.", null, null, null, null));
                    authenticated = true;
                }
                else
                {*/
                    // Authenticate from request headers.
                    if (registration == null)
                    {
                        // SIP account does not exist so send 403.
                        FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.Registrar, ProxyEventTypesEnum.Warn, "Forbidden " + addressOfRecord.ToString() + " does not exist, " + IPSocket.GetSocketString(sipRequest.Header.Via.BottomViaHeader.GetEndPoint()) + ", " + sipRequest.Header.UserAgent + ".", null));
                        SIPResponse forbiddenResponse = GetErrorResponse(sipRequest.Header, SIPResponseStatusCodesEnum.Forbidden, null);
                        registerTransaction.SendFinalResponse(forbiddenResponse);
                        return RegisterResultEnum.Forbidden;
                    }
                    else if (sipRequest.Header.AuthenticationHeader != null)
                    {
                        SIPAuthenticationHeader reqAuthHeader = sipRequest.Header.AuthenticationHeader;
                        string requestNonce = (reqAuthHeader != null) ? reqAuthHeader.AuthRequest.Nonce : null;

                        #region Checking digest.

                        if (registration.Password == null || registration.Password.Trim().Length == 0)
                        {
                            // Allow blank password.
                            authenticated = true;
                        }
                        else
                        {
                            string realm = reqAuthHeader.AuthRequest.Realm;
                            string uri = reqAuthHeader.AuthRequest.URI;
                            string response = reqAuthHeader.AuthRequest.Response;
                            string authUsername = addressOfRecord.User;
                            string secret = registration.Password;

                            AuthorizationRequest checkAuthReq = new AuthorizationRequest(realm, authUsername, secret, uri, requestNonce, SIPMethodsEnum.REGISTER.ToString());
                            string digest = checkAuthReq.Digest;

                            authenticated = (digest == response);

                            if (!authenticated)
                            {
                                FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.Registrar, ProxyEventTypesEnum.RegisterFail, "Registration authentication failed for " + addressOfRecord.ToString() + " from " + registerTransaction.Protocol + ":" + registerTransaction.RemoteEndPoint + ".", null));
                            }
                        }

                        #endregion
                    }
             //   }

                if (!authenticated)
                {
                    // 401 Response with a fresh nonce needs to be sent.
                    //SIPResponse authReqdResponse = GetAuthReqdResponse(sipRequest.Header, Crypto.GetRandomInt().ToString(), addressOfRecord.Host);
                    string realm = registerURI.Host;
                    if (m_strictRealmHandling)
                    {
                        // Lookup the realm to ensure it is being looked after by this registrar.
                        realm = m_registrationsStore.GetDomain(registerURI.Host);
                    }

                    if (realm == null)
                    {
                        SIPResponse noRealmResponse = GetErrorResponse(sipRequest.Header, SIPResponseStatusCodesEnum.NotFound, "Domain not serviced");
                        registerTransaction.SendFinalResponse(noRealmResponse);
                        FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.Registrar, ProxyEventTypesEnum.Warn, "Registration request received for non-serviced domain " + registerURI.Host + " from " + registerTransaction.Protocol + ":" + registerTransaction.RemoteEndPoint + ".", null));
                        return RegisterResultEnum.DomainNotServiced;
                    }
                    else
                    {
                        SIPResponse authReqdResponse = GetAuthReqdResponse(sipRequest.Header, Crypto.GetRandomInt().ToString(), realm);
                        registerTransaction.SendFinalResponse(authReqdResponse);
                        FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.Registrar, ProxyEventTypesEnum.Registrar, "Authentication required for " + addressOfRecord.ToString() + " from " + registerTransaction.Protocol + ":" + registerTransaction.RemoteEndPoint + ".", addressOfRecord.User));
                        return RegisterResultEnum.AuthenticationRequired;
                    }
                }
                else // Authenticated.
                {
                    //registration.ProxyEndPoint = remoteEndPoint;
                    registration.LastAuthenticationTime = DateTime.Now;
                    
                    if (sipRequest.Header.Contact == null || sipRequest.Header.Contact.Count == 0)
                    {
                        // No contacts header to update bindings with, return a list of the current bindings.
                        List<SIPContactHeader> contactsList = registration.GetContactHeader(true, null);
                        if (contactsList != null)
                        {
                            sipRequest.Header.Contact = contactsList;
                        }

                        SIPResponse okResponse = GetOkResponse(sipRequest.Header, false);
                        registerTransaction.SendFinalResponse(okResponse);
                        FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.Registrar, ProxyEventTypesEnum.RegisterSuccess, "Empty registration request successful for " + addressOfRecord.ToString() + " from " + registerTransaction.Protocol + ":" + registerTransaction.RemoteEndPoint + ".", addressOfRecord.User));
                    }
                    else
                    {
                        //logger.Debug("Recording registration record for " + addressOfRecord.ToString() + ".");

                        IPEndPoint uacRecvdEndPoint = registerTransaction.RemoteEndPoint;
                        IPEndPoint proxyEndPoint = null;
                        SIPProtocolsEnum proxyProtocol = SIPProtocolsEnum.UDP;
                        IPEndPoint registrarEndPoint = registerTransaction.SendFromEndPoint;
                        
                        if (sipRequest.Header.Via.Length > 1) // Can't be a downstream proxy if there is only one Via.
                        {
                            string proxySocket = sipRequest.Header.Via.Via[1].ViaParameters.Get(PROXY_VIA_PARAMETER_NAME);

                            if (proxySocket != null)
                            {
                                proxyEndPoint = IPSocket.ParseSocketString(proxySocket);
                                proxyProtocol = sipRequest.Header.Via.Via[1].Transport; 
                                uacRecvdEndPoint = sipRequest.Header.Via.Via[1].GetEndPoint();  // The second top Via header which will be the SIP element prior to the SIP Proxy.
                            }
                        }

                        SIPResponseStatusCodesEnum updateResult = SIPResponseStatusCodesEnum.Ok;
                        string updateMessage = null;

                        bool bindingsUpdated = registration.UpdateBinding(
                            proxyEndPoint,
                            proxyProtocol,
                            uacRecvdEndPoint,
                            registrarEndPoint,
                            addressOfRecord.User,
                            sipRequest.Header.Contact,
                            sipRequest.Header.CallId,
                            sipRequest.Header.CSeq,
                            sipRequest.Header.Expires,
                            sipRequest.Header.UserAgent,
                            m_maximumUserBindings,
                            GetUserAgentExpiry(sipRequest.Header.UserAgent),
                            out updateResult,
                            out updateMessage);

                        if (bindingsUpdated)
                        {
                            m_registrationsStore.AddDirtyRegistration(registration);
                        }

                        if (updateResult == SIPResponseStatusCodesEnum.Ok)
                        {
                            // The standard states that the Ok response should contain the list of current bindings but that breaks a lot of UAs. As a 
                            // compromise the list is returned with the Contact that UAC sent as the first one in the list.
                            bool contactListSupported = true;
                            if (sipRequest.Header.UserAgent != null)
                            {
                                contactListSupported = GetUserAgentContactListSupport(sipRequest.Header.UserAgent);
                            }

                            if (contactListSupported)
                            {
                                sipRequest.Header.Contact = registration.GetContactHeader(true, sipRequest.Header.Contact[0].ContactURI);
                            }
                            else
                            {
                                List<SIPContactHeader> bindings = registration.GetContactHeader(false, sipRequest.Header.Contact[0].ContactURI);

                                if (bindings != null && bindings.Count > 0)
                                {
                                    sipRequest.Header.Contact[0].Expires = bindings[0].Expires;
                                }
                            }
                            
                            
                            SIPResponse okResponse = GetOkResponse(sipRequest.Header, registration.NATSendKeepAlives);
                            registerTransaction.SendFinalResponse(okResponse);
                           
                            if (registration.HasCurrentBinding())
                            {
                                string proxySocketStr = (proxyEndPoint != null) ? " (proxy=" + proxyEndPoint.ToString() + ")" : null;
                                int expiry = sipRequest.Header.Contact[0].Expires;
                                FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.Registrar, ProxyEventTypesEnum.RegisterSuccess, "Registration successful for " + addressOfRecord.ToString() + " from " + registerTransaction.Protocol + ":" + registerTransaction.RemoteEndPoint + proxySocketStr + ", expiry " + expiry + "s.", addressOfRecord.User));
                            }
                            else
                            {
                                FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.Registrar, ProxyEventTypesEnum.RegisterSuccess, "Registration successful for " + addressOfRecord.ToString() + " from " + registerTransaction.Protocol + ":" + registerTransaction.RemoteEndPoint + ", no current bindings.", addressOfRecord.User));
                            }
                        }
                        else
                        {
                            SIPResponse errorResponse = GetErrorResponse(sipRequest.Header, updateResult, updateMessage);
                            registerTransaction.SendFinalResponse(errorResponse);
                            FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.Registrar, ProxyEventTypesEnum.Error, "Registration request successful but binding update failed for " + addressOfRecord.ToString() + " from " + registerTransaction.Protocol + ":" + registerTransaction.RemoteEndPoint + ".", addressOfRecord.User));
                        }
                    }

                    return RegisterResultEnum.Authenticated;
                }
            }
            catch (Exception excp)
            {
                string regErrorMessage = "Exception registrarcore registering. " + excp.Message + "\r\n" + sipRequest.ToString();
                logger.Error(regErrorMessage);
                FireProxyLogEvent(new ProxyMonitorEvent(ProxyServerTypesEnum.Registrar, ProxyEventTypesEnum.Error, regErrorMessage, null));

                try
                {
                    SIPResponse errorResponse = GetErrorResponse(sipRequest.Header, SIPResponseStatusCodesEnum.InternalServerError, null);
                    registerTransaction.SendFinalResponse(errorResponse);
                }
                catch { }

                return RegisterResultEnum.Error;
            }
        }

        /// <summary>
        /// Makes a decision on what the maximum allowed expiry is for a REGISTER request. Allows different expiry values to be accepted from different user agents.
        /// This is useful as some user agents ignore the expiry value set by the server and setting a higher value for that user agent can stop the registrar
        /// expiring it.
        /// </summary>
        /// <param name="userAgent">The useragent to get the maximum expiry for.</param>
        /// <returns>The maximum expiry value that will be accepted.</returns>
        private int GetUserAgentExpiry(string userAgent)
        {
            int expiry = DEFAULT_EXPIRY_SECONDS;

            try
            {
                if (m_userAgentConfigs != null && m_userAgentConfigs.Count > 0)
                {
                    bool userAgentMatchFound = false;
                    if (userAgent != null && userAgent.Trim().Length > 0)
                    {
                        foreach (string userAgentPattern in m_userAgentConfigs.Keys)
                        {
                            if (Regex.Match(userAgent, userAgentPattern, RegexOptions.IgnoreCase).Success)
                            {
                                expiry = m_userAgentConfigs[userAgentPattern].MaxAllowedExpiryTime;
                                userAgentMatchFound = true;
                                break;
                            }
                        }
                    }

                    if (!userAgentMatchFound)
                    {
                        expiry = DEFAULT_EXPIRY_SECONDS;
                    }
                }

                //logger.Debug("Expiry for " + userAgent + " is " + expiry + "s, request was " + requestedExpiry + ".");
                return expiry;
            }
            catch (Exception excp)
            {
                logger.Error("Exception GetUserAgentExpiry. " + excp);
                return expiry;
            }
        }

        /// <summary>
        /// Makes a decision on whether the user agent supports a list of current contacts being returned in the Ok response as mandated by the SIP standard
        /// or whether it is broken and will only work if the exact header from the request is returned.
        /// </summary>
        /// <param name="userAgent">The useragent to check whether contact lists are supported or not..</param>
        /// <returns>True if the useragent supports the standard and lists false otherwise.</returns>
        private bool GetUserAgentContactListSupport(string userAgent)
        {
            bool listSupported = true;

            try
            {
                if (m_userAgentConfigs != null && m_userAgentConfigs.Count > 0)
                {
                    if (userAgent != null && userAgent.Trim().Length > 0)
                    {
                        foreach (string userAgentPattern in m_userAgentConfigs.Keys)
                        {
                            if (Regex.Match(userAgent, userAgentPattern, RegexOptions.IgnoreCase).Success)
                            {
                                listSupported = m_userAgentConfigs[userAgentPattern].ContactListSupported;
                                break;
                            }
                        }
                    }
                }

                return listSupported;
            }
            catch (Exception excp)
            {
                logger.Error("Exception GetUserAgentContactListSupport. " + excp);
                return true;
            }
        }

        private SIPResponse GetOkResponse(SIPHeader requestHeader, bool requestProxyNATKeepAlives)
		{	
			try
			{
				SIPResponse okResponse = new SIPResponse(SIPResponseStatusCodesEnum.Ok);
                okResponse.Header = new SIPHeader(requestHeader.Contact, requestHeader.From, requestHeader.To, requestHeader.CSeq, requestHeader.CallId);

                if (okResponse.Header.To.ToTag == null || okResponse.Header.To.ToTag.Trim().Length == 0)
                {
                    okResponse.Header.To.ToTag = CallProperties.CreateNewTag();
                }

                okResponse.Header.CSeqMethod = requestHeader.CSeqMethod;
				okResponse.Header.Via = requestHeader.Via;
                okResponse.Header.Server = m_serverAgent;
				okResponse.Header.MaxForwards = Int32.MinValue;
                okResponse.Header.SetDateHeader();

				return okResponse;
			}
			catch(Exception excp)
			{
				logger.Error("Exception GetOkResponse. " + excp.Message);
				throw excp;
			}
		}
	
		private SIPResponse GetAuthReqdResponse(SIPHeader requestHeader, string nonce, string realm)
		{
			try
			{
				if(requestHeader == null)
				{
					throw new ApplicationException("Cannot build Authorisation Required response from an empty request header.");
				}

				SIPResponse authReqdResponse = new SIPResponse(SIPResponseStatusCodesEnum.Unauthorised);
				SIPAuthenticationHeader authHeader = new SIPAuthenticationHeader(realm, nonce);
				
				SIPHeader unauthHeader = new SIPHeader(requestHeader.Contact, requestHeader.From, requestHeader.To, requestHeader.CSeq, requestHeader.CallId);

                if (unauthHeader.To.ToTag == null || unauthHeader.To.ToTag.Trim().Length == 0)
                {
                    unauthHeader.To.ToTag = CallProperties.CreateNewTag();
                }

				unauthHeader.CSeqMethod = requestHeader.CSeqMethod;
				unauthHeader.Via = requestHeader.Via;
				unauthHeader.WWWAuthenticate = true;
				unauthHeader.AuthenticationHeader = authHeader;
                unauthHeader.Server = m_serverAgent;
				unauthHeader.MaxForwards = Int32.MinValue;

				authReqdResponse.Header = unauthHeader;
			
				return authReqdResponse;
			}
			catch(Exception excp)
			{
				logger.Error("Exception GetAuthReqdResponse. " + excp.Message);
				throw excp;
			}
		}
		
		private SIPResponse GetErrorResponse(SIPHeader requestHeader, SIPResponseStatusCodesEnum errorResponseCode, string errorMessage)
		{
			try
			{
				if(requestHeader == null)
				{
					throw new ApplicationException("Cannot build Error response from an empty request header.");
				}

                SIPResponse errorResponse = new SIPResponse(errorResponseCode);
                if (errorMessage != null)
                {
                    errorResponse.ReasonPhrase = errorMessage;
                }
							
				SIPHeader errorHeader = new SIPHeader(requestHeader.Contact, requestHeader.From, requestHeader.To, requestHeader.CSeq, requestHeader.CallId);

                if (errorHeader.To.ToTag == null || errorHeader.To.ToTag.Trim().Length == 0)
                {
                    errorHeader.To.ToTag = CallProperties.CreateNewTag();
                }
                
                errorHeader.CSeqMethod = requestHeader.CSeqMethod;
                errorHeader.Via = requestHeader.Via;
                errorHeader.Server = m_serverAgent;
                errorHeader.MaxForwards = Int32.MinValue;

                errorResponse.Header = errorHeader;
			
				return errorResponse;
			}
			catch(Exception excp)
			{
				logger.Error("Exception GetErrorResponse. " + excp.Message);
				throw excp;
			}
		}
		
        private void FireProxyLogEvent(ProxyMonitorEvent monitorEvent)
        {
            if (m_registrarLogEvent != null)
            {
                try
                {
                    m_registrarLogEvent(monitorEvent);
                }
                catch (Exception excp)
                {
                    logger.Error("Exception FireProxyLogEvent RegistrarCore. " + excp.Message);
                }
            }
        }
	}
}
